
package com.borqs.music.http;

import static com.borqs.music.http.HttpResponseCode.BAD_REQUEST;
import static com.borqs.music.http.HttpResponseCode.ENHANCE_YOUR_CLAIM;
import static com.borqs.music.http.HttpResponseCode.INTERNAL_SERVER_ERROR;
import static com.borqs.music.http.HttpResponseCode.MULTIPLE_CHOICES;
import static com.borqs.music.http.HttpResponseCode.OK;
import static com.borqs.music.http.HttpResponseCode.SERVICE_UNAVAILABLE;
import static com.borqs.music.http.RequestMethod.POST;

import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

import com.borqs.music.util.HttpRequestUtils;

public class MusicHttpClientWrapper {
    private String proxyHost = null;
    private int proxyPort = -1;
    private boolean isUseProxy = false;
    private int connectionTimeout = 20000;
    private int readTimeout = 120000;
    private int retryCount = 0;
    private int retryIntervalSeconds = 5 * 1000;

    private MusicHttpClientWrapper(HttpConfiguration conf) {
        setUseProxy(conf.isUseProxy());
        setProxyHost(conf.getHttpProxyHost());
        setProxyPort(conf.getHttpProxyPort());
        setConnectionTimeout(conf.getHttpConnectionTimeout());
        setReadTimeout(conf.getHttpReadTimeout());
        setRetryCount(conf.getHttpRetryCount());
        setRetryIntervalSeconds(conf.getHttpRetryInterval());
    }

    public void shutdown() {
    }

    private static final Map<HttpConfiguration, MusicHttpClientWrapper> instanceMap = 
                                                new HashMap<HttpConfiguration, MusicHttpClientWrapper>(1);

    public static MusicHttpClientWrapper getInstance(HttpConfiguration conf) {
        MusicHttpClientWrapper client = instanceMap.get(conf);
        if (null == client) {
            client = new MusicHttpClientWrapper(conf);
            instanceMap.put(conf, client);
        }
        return client;
    }

    public void setProxyHost(String proxyHost) {
        this.proxyHost = proxyHost;
    }

    public String getProxyHost() {
        return proxyHost;
    }

    /**
     * Sets use proxy host or not.
     */
    public void setUseProxy(boolean flag) {
        this.isUseProxy = flag;
    }

    /**
     * Get use proxy host or not.
     */
    public boolean getUseProxy() {
        return this.isUseProxy;
    }

    public static String UserAgent = "os=android-8-arm;client=com.borqs.qiupu-110-arm;lang=zh_CN;model=OMS";

    public static void setUserAgent(String ua) {
        UserAgent = ua;
    }

    public int getProxyPort() {
        return proxyPort;
    }

    /**
     * Sets proxy port.
     * 
     * @param proxyPort
     */
    public void setProxyPort(int proxyPort) {
        this.proxyPort = proxyPort;
    }

    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    /**
     * Sets a specified timeout value, in milliseconds, to be used when opening
     * a communications link to the resource referenced by this URLConnection.
     * 
     * @param connectionTimeout - an int that specifies the connect timeout
     *            value in milliseconds
     */
    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;

    }

    public int getReadTimeout() {
        return readTimeout;
    }

    /**
     * Sets the read timeout to a specified timeout, in milliseconds.
     * 
     * @param readTimeout - an int that specifies the timeout value to be used
     *            in milliseconds
     */
    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public void setRetryCount(int retryCount) {
        if (retryCount >= 0) {
            this.retryCount = retryCount;
        } else {
            throw new IllegalArgumentException("RetryCount cannot be negative.");
        }
    }

    public void setRetryIntervalSeconds(int retryIntervalSeconds) {
        if (retryIntervalSeconds >= 0) {
            this.retryIntervalSeconds = retryIntervalSeconds;
        } else {
            throw new IllegalArgumentException(
                    "RetryInterval cannot be negative.");
        }
    }

    /**
     * sets HTTP headers
     * 
     * @param req The request
     * @param connection HttpURLConnection
     */
    private void setHeaders(HttpRequest req, HttpURLConnection connection) {
        Map<String, String> headers = req.getRequestHeaders();
        if (null != headers) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                connection.addRequestProperty(entry.getKey(), entry.getValue());
            }
        }
    }

    private HttpURLConnection getConnection(String url) throws IOException {
        HttpURLConnection con = null;
        if (isUseProxy) {
            // FIXME when need proxy
            final Proxy proxy = new Proxy(Proxy.Type.HTTP,
                    InetSocketAddress.createUnresolved(proxyHost, proxyPort));
            con = (HttpURLConnection) new URL(url).openConnection(proxy);
        } else {
            con = (HttpURLConnection) new URL(url).openConnection();
        }

        con.setConnectTimeout(connectionTimeout);
        con.setReadTimeout(readTimeout);
        con.setRequestProperty("User-Agent", UserAgent);
        return con;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof MusicHttpClientWrapper)) {
            return false;
        }

        MusicHttpClientWrapper that = (MusicHttpClientWrapper) o;

        if (connectionTimeout != that.connectionTimeout) {
            return false;
        }
        
        if (isUseProxy != that.isUseProxy) {
            return false;
        }
        
        if (proxyPort != that.proxyPort) {
            return false;
        }
        if (readTimeout != that.readTimeout) {
            return false;
        }
        if (retryCount != that.retryCount) {
            return false;
        }
        
        if (retryIntervalSeconds != that.retryIntervalSeconds) {
            return false;
        }

        if (proxyHost != null ? !proxyHost.equals(that.proxyHost) : that.proxyHost != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = proxyHost != null ? proxyHost.hashCode() : 0;
        result = 31 * result + proxyPort;
        result = 31 * result + (isUseProxy ? 1 : 0);
        result = 31 * result + connectionTimeout;
        result = 31 * result + readTimeout;
        result = 31 * result + retryCount;
        result = 31 * result + retryIntervalSeconds;
        return result;
    }

    @Override
    public String toString() {
        return "HttpClientImpl{" +
                "useProxy=" + isUseProxy +
                ", proxyHost='" + proxyHost + '\'' +
                ", proxyPort=" + proxyPort +
                ", connectionTimeout=" + connectionTimeout +
                ", readTimeout=" + readTimeout +
                ", retryCount=" + retryCount +
                ", retryIntervalSeconds=" + retryIntervalSeconds +
                '}';
    }

    public HttpResponse request(HttpRequest req) throws Exception {
        int retriedCount = 0;
        int retry = retryCount + 1;
        
        HttpResponse res = null;
        for (retriedCount = 0; retriedCount < retry; retriedCount++) {
            int responseCode = -1;
            HttpURLConnection con = null;
            OutputStream os = null;
            try {
                con = getConnection(req.getURL());
                con.setDoInput(true);
                setHeaders(req, con);
                con.setRequestMethod(req.getMethod().name());
                if (req.getMethod() == POST) {
                    final RequestListener listener = req.getListener();
                    final Map<String, File> fileMap = req.getFileMap();
                    if (fileMap != null && fileMap.size() > 0) {
                        String boundary = "----BorqsMusic-upload" + System.currentTimeMillis();
                        con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
                        con.setRequestProperty("Transfer-Encoding", "chunked");

                        boundary = "--" + boundary;

                        con.setDoOutput(true);
                        os = con.getOutputStream();
                        DataOutputStream out = new DataOutputStream(os);
                        // Transfer the parameters
                        Map<String, String> parameters = req.getParameters();
                        for (Map.Entry<String, String> param : parameters.entrySet()) {
                            out.writeBytes(boundary + "\r\n");
                            out.writeBytes("Content-Disposition: form-data; name=\"" + param.getKey() + "\"\r\n");
                            out.writeBytes("Content-Type: text/plain; charset=UTF-8\r\n\r\n");
                            out.write(param.getValue().getBytes("UTF-8"));
                            out.writeBytes("\r\n");
                        }

                        // Transfer the files
                        for (Map.Entry<String, File> param : fileMap.entrySet()) {
                            String name = param.getKey();
                            File file = param.getValue();
                            out.writeBytes(boundary + "\r\n");
                            String fileName = URLEncoder.encode(file.getName(), "UTF-8");
                            out.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + fileName + "\"\r\n");
                            out.writeBytes("Content-Type: " + HttpRequestUtils.getContentType(file) + "\r\n\r\n");
                            BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
                            int bytes = 0;
                            byte[] buffer = new byte[32 * 1024];

                            long filesize = file.length();
                            long processedsize = 0;
                            while ((bytes = in.read(buffer)) != -1) {
                                out.write(buffer, 0, bytes);
                                processedsize += bytes;

                                if (listener != null) {
                                    if (((int) ((processedsize / (1.f * filesize)) * 100)) % 10 == 0) {
                                        listener.onRequest(processedsize, filesize);
                                    }
                                }
                            }
                            out.writeBytes("\r\n");
                            in.close();
                        }
                        out.writeBytes(boundary + "--\r\n");
                        out.writeBytes("\r\n");
                    } else {
                        con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                        String postParam = HttpRequestUtils.encodeParameters(req.getParameters());
                        byte[] bytes = postParam.getBytes("UTF-8");
                        con.setRequestProperty("Content-Length", Integer.toString(bytes.length));
                        con.setDoOutput(true);
                        os = con.getOutputStream();
                        os.write(bytes);
                    } // end check file map
                    os.flush();
                    os.close();
                }
                res = new HttpResponseImpl(con);
                responseCode = con.getResponseCode();
                if (responseCode < OK || MULTIPLE_CHOICES <= responseCode) {
                    if (responseCode == ENHANCE_YOUR_CLAIM ||
                            responseCode == SERVICE_UNAVAILABLE ||
                            responseCode == BAD_REQUEST ||
                            responseCode < INTERNAL_SERVER_ERROR ||
                            retriedCount == retryCount) {
                        
                        throw new Exception("Response Code =  " + responseCode + "\n"
                                + res.asString());
                    }
                    // will retry if the status code is INTERNAL_SERVER_ERROR
                } else {
                    break;
                }
            } catch (Exception ioe) {
                ioe.printStackTrace();
                if (retriedCount == retryCount) {
                    throw ioe;
                }
            } finally {
                try {
                    os.close();
                } catch (Exception ignore) {
                }
            }

            try {
                Thread.sleep(retryIntervalSeconds * 1000);
            } catch (InterruptedException ignore) {
                // nothing to do
            }
        }// end for

        return res;
    }

}
